package org.ctd.springframework.beans.factory.xml;

import com.ctd.springframework.core.NamedThreadLocal;
import com.ctd.springframework.core.io.Resource;
import com.ctd.springframework.core.io.ResourceLoader;
import com.ctd.springframework.core.io.support.EncodedResource;
import com.ctd.springframework.util.Assert;
import com.ctd.springframework.util.xml.SimpleSaxErrorHandler;
import com.ctd.springframework.util.xml.XmlValidationModeDetector;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.ctd.springframework.beans.factory.BeanDefinitionStoreException;
import org.ctd.springframework.beans.factory.parsing.extractor.source.NullSourceExtractor;
import org.ctd.springframework.beans.factory.parsing.extractor.source.SourceExtractor;
import org.ctd.springframework.beans.factory.parsing.listener.reader.EmptyReaderEventListener;
import org.ctd.springframework.beans.factory.parsing.listener.reader.ReaderEventListener;
import org.ctd.springframework.beans.factory.parsing.reporter.FailFastProblemReporter;
import org.ctd.springframework.beans.factory.parsing.reporter.ProblemReporter;
import org.ctd.springframework.beans.factory.support.AbstractBeanDefinitionReader;
import org.ctd.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.ctd.springframework.beans.handler.namespace.NamespaceHandlerResolver;
import org.ctd.springframework.beans.utils.bean.BeanUtils;
import org.w3c.dom.Document;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

/**
 * XmlBeanDefinitionReader
 * spring 自定义 xml 读取器
 *
 * @author chentudong
 * @time 2019/3/24 20:34
 */
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
    private static final Log logger = LogFactory.getLog(DefaultDocumentLoader.class);

    public static final int VALIDATION_AUTO = XmlValidationModeDetector.VALIDATION_AUTO;
    public static final int VALIDATION_XSD = XmlValidationModeDetector.VALIDATION_XSD;

    private int validationMode = VALIDATION_AUTO;

    /**
     * 命名空间
     */
    private boolean namespaceAware = false;

    /**
     * xml  errorHandler
     */
    private ErrorHandler errorHandler = new SimpleSaxErrorHandler(logger);

    /**
     * 解析xml 文件
     */
    private final XmlValidationModeDetector validationModeDetector = new XmlValidationModeDetector();

    private EntityResolver entityResolver;
    /**
     * 加载 document
     */
    private DocumentLoader documentLoader = new DefaultDocumentLoader();

    private ProblemReporter problemReporter = new FailFastProblemReporter();

    private ReaderEventListener eventListener = new EmptyReaderEventListener();

    private SourceExtractor sourceExtractor = new NullSourceExtractor();

    private Class<?> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;

    private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded = new NamedThreadLocal<>(
            "XML bean definition resources currently being loaded");

    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
        super(registry);
    }

    /**
     * 获取 BeanDefinitionRegistry
     *
     * @return BeanDefinitionRegistry
     */
    @Override
    public BeanDefinitionRegistry getRegistry() {
        return null;
    }

    /**
     * 加载bean 的个数
     *
     * @param resource resource
     * @return int
     * @throws BeanDefinitionStoreException BeanDefinitionStoreException
     */
    @Override
    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        return loadBeanDefinitions(new EncodedResource(resource));
    }

    /**
     * encodedResource 编码解析xml 文件
     * 并统计 加载bean的个数
     *
     * @param encodedResource encodedResource
     * @return int
     */
    private int loadBeanDefinitions(EncodedResource encodedResource) {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        //获取已加载的 EncodedResource
        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (Objects.isNull(currentResources)) {
            currentResources = new HashSet<>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        if (!currentResources.add(encodedResource)) {
            //encodedResource 已存在set 集合， 检查到该元素的循环加载
            throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
        //获取输入流
        try {
            try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
                //转换为 InputSource 由xml定义的流读取
                InputSource inputSource = new InputSource(inputStream);
                //判断是否指定编码
                if (Objects.nonNull(encodedResource.getEncoding())) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
        } catch (IOException e) {
            e.printStackTrace();
            throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), e);
        } finally {
            //删除 encodedResource
            currentResources.remove(encodedResource);
            if (currentResources.isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
    }

    /**
     * 解析 xml
     *
     * @param inputSource inputSource
     * @param resource    resource
     * @return int
     */
    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) {
        try {
            //将XML文件转换为DOM对象，解析过程由documentLoader实现
            Document doc = doLoadDocument(inputSource, resource);

            //解析doc 注册bean,  
            return registerBeanDefinitions(doc, resource);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0;
    }

    /**
     * 解析 doc 注册 BeanDefinition
     * 这里是启动对Bean定义解析的详细过程，该解析过程会用到Spring的Bean配置规则
     *
     * @param doc      doc
     * @param resource resource
     * @return int
     * @throws BeanDefinitionStoreException BeanDefinitionStoreException
     */
    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        //得到BeanDefinitionDocumentReader来对xml格式的BeanDefinition解析
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        createReaderContext(resource);
        return 0;
    }

    private XmlReaderContext createReaderContext(Resource resource) {
        return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
                this.sourceExtractor, this, getNamespaceHandlerResolver());
    }

    private NamespaceHandlerResolver getNamespaceHandlerResolver() {
        return null;
    }

    /**
     * 解析 xml
     * 委托给
     *
     * @param inputSource inputSource
     * @param resource    resource
     * @return Document
     * @throws Exception Exception
     * @see DefaultDocumentLoader
     */
    private Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
        //加载 Document
        return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                getValidationModeForResource(resource), isNamespaceAware());
    }

    protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
        return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));
    }

    private boolean isNamespaceAware() {
        return this.namespaceAware;
    }

    protected EntityResolver getEntityResolver() {
        if (Objects.isNull(this.entityResolver)) {
            ResourceLoader resourceLoader = getResourceLoader();
            //判断采用哪种方式解析xml sxd 或 dtd
            if (Objects.nonNull(resourceLoader)) {
                this.entityResolver = new ResourceEntityResolver(resourceLoader);
            } else {
                this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
            }
        }
        return this.entityResolver;
    }

    /**
     * 判断采用哪种方式读取配置文件
     *
     * @param resource resource
     * @return int
     */
    protected int getValidationModeForResource(Resource resource) {
        int validationModeToUse = getValidationMode();
        if (validationModeToUse != VALIDATION_AUTO) {
            return validationModeToUse;
        }
        int detectedMode = detectValidationMode(resource);
        if (detectedMode != VALIDATION_AUTO) {
            return detectedMode;
        }
        // Hmm, we didn't get a clear indication... Let's assume XSD,
        // since apparently no DTD declaration has been found up until
        // detection stopped (before finding the document's root tag).
        return VALIDATION_XSD;
    }

    protected int detectValidationMode(Resource resource) {
        //判断文件是否已经打开
        if (resource.isOpen()) {
            throw new BeanDefinitionStoreException(
                    "Passed-in Resource [" + resource + "] contains an open stream: " +
                            "cannot determine validation mode automatically. Either pass in a Resource " +
                            "that is able to create fresh streams, or explicitly specify the validationMode " +
                            "on your XmlBeanDefinitionReader instance.");
        }
        InputStream inputStream;
        try {
            inputStream = resource.getInputStream();
        } catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
                            "Did you attempt to load directly from a SAX InputSource without specifying the " +
                            "validationMode on your XmlBeanDefinitionReader instance?", ex);
        }

        try {
            return this.validationModeDetector.detectValidationMode(inputStream);
        } catch (IOException ex) {
            throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
                    resource + "]: an error occurred whilst reading from the InputStream.", ex);
        }
    }

    public int getValidationMode() {
        return this.validationMode;
    }
}
